function MVVM(options) {
  this.$options = options;
  // 原data数据
  var data = (this._data = this.$options.data);
  // 将this存储起来，将来在其他函数中使用
  var me = this;

  // 1. 数据代理：将data中的数据挂载到this上，将来可以通过this直接访问data数据
  // 优点：访问data数据更加方便
  // 遍历data所有属性，对每一个属性进行数据代理
  // 数据代理通过Object.defineProperty方法，给this添加data属性，并设置属性读取的方法get，属性设置的方法set
  // 当你将来读取this上的属性时，触发get方法，实际读取原data数据的值
  // 当你将来设置this上的属性时，触发set方法，实际设置原data数据的值
  Object.keys(data).forEach(function (key) {
    me._proxy(key);
  });

  // 2. 数据劫持：对原data数据上的所有属性进行重新定义，定义成响应式
  // 遍历data中所有属性，通过defineReactive方法对属性进行重新定义，定义成响应式
  // 每一data数据都有产生dep，这个dep通过闭包的方式保存在get和set中
  observe(data, this);

  // 3. 模板解析：解析模板页面中的模板语法（插值语法和指令语法）
  /*
    1. 将元素节点添加到文档碎片节点中
    2. 解析其中模板语法
      提取所有子节点进行遍历
        判断当前子节点是否元素节点（最终目的：为了解析元素上指令语法）
          提取子节点所有属性节点进行遍历
            判断是否是指令属性（v-）
              判断是否是事件指令（on）
                给当前子节点绑定事件和回调函数，回调函数改变this指向为vm
              还是普通指令
                v-text textUpdater 设置元素textContent属性，值为表达式的值 
                v-html htmlUpdater 设置元素innerHTML属性，值为表达式的值 
            删除已解析的属性
        判断当前子节点是否文本节点并且是否有插值语法（最终目的：为了解析插值语法）
          textUpdater 设置元素textContent属性，值为表达式的值
        判断当前子节点是否还有子节点，如果有就递归遍历子节点的子节点（最终目的：所有节点得到编译）
    3. 将解析后的文档碎片节点添加到el中，显示内容
  */
  this.$compile = new Compile(options.el || document.body, this);
}

MVVM.prototype = {
  $watch: function (key, cb, options) {
    new Watcher(this, key, cb);
  },

  // 数据代理的方法
  _proxy: function (key) {
    var me = this;

    Object.defineProperty(me, key, {
      configurable: false,
      enumerable: true,
      get: function proxyGetter() {
        return me._data[key];
      },
      set: function proxySetter(newVal) {
        // 更新原data数据
        // 触发数据劫持set
        me._data[key] = newVal;
      },
    });
  },
};
